grammar Query;

query		:	SELECT 
				(
					DISTINCT
					{
						builder.setDistinct();
					}
				)? columns
				FROM from 
				(join)*
				{
					builder.resolveColumns();
				}
				(where)?
				(GROUP BY groups)?
				(having)?
				(union)*
				(ORDER BY orders)?
			;

columns		:	ASTERISK
				{
					builder.queryAllColumns();
				}
			|	column (COMMA column)*
			;
			
			
column		:	ID PERIOD ASTERISK
				{
					builder.addAllColumns($ID.text);
				}
			|
				{
					String tableAlias = null;
					String columnName = null;
					String alias = null;
				}
				(	
					ID
					{
						columnName = $ID.text;
					}
				|	ID PERIOD NAME = ID
					{
						tableAlias = $ID.text;
						columnName = $NAME.text;
					}
				)
				(
					AS? ID
					{
						alias = $ID.text;
					}
				)?
				{
					if (tableAlias == null) {
						if (alias == null){
							builder.addColumn(columnName);
						} else {
							builder.addAliasColumn(columnName, alias);
						}
					} else {
						if (alias == null){
							builder.addColumn(tableAlias, columnName);
						} else {
							builder.addAliasColumn(tableAlias, columnName, alias);
						}
					}
				}
			|	{
					String alias = null;
				}
				exp = expression 
				(
					AS? ID
					{
						alias = $ID.text;
					}
				)?
				{
					if(alias == null){
						throw Lang.makeThrow("You must set an alias for this column: %s.", $exp.text);
					}
					builder.addExpColumn(alias);
				}
			;
			
from		:	{
					String alias = null;
				}
				TABLE = ID 
				(
					AS? ID
					{
						alias = $ID.text;
					}
				)?
				{
					if (alias == null) {
						builder.setFrom($TABLE.text);
					} else {
						builder.setFrom($TABLE.text, alias);
					}
				}
			|	L_BRACKET
				{
					beginSubQuery();
					String alias = null;
				} 
				query R_BRACKET 
				(
					AS? ID
					{
						alias = $ID.text;
					}
				)?
				{
					if(alias == null){
						throw Lang.makeThrow("You must set an alias at the end of the from.");
					}
					setSubQueryFrom(alias);
				}
			;
			
join		:	op=(INNER | LEFT)
				{
					String alias = null;
				} 
				JOIN TABLE=ID 
				(
					AS? ID
					{
						alias = $ID.text;
					}
				)? ON condition
				{
					if (alias == null) {
						builder.addJoin($op.type == INNER, $TABLE.text);
					} else {
						builder.addJoin($op.type == INNER, $TABLE.text, alias);
					}
				}
			;

where		:	WHERE condition
				{
					builder.setWhere();
				}
			;
						
groups		:	group (COMMA group)*;

group		:	ID
				{
					builder.addGroup($ID.text);
				}
			|	TABLE = ID PERIOD ID
				{
					builder.addGroup($TABLE.text, $ID.text);
				}
			|	ID PERIOD ASTERISK
				{
					builder.addAllGroups($ID.text);
				}
			;

having		:	HAVING condition
				{
					builder.setHaving();
				}
			;

union		:	UNION 
				{
					boolean isAll = false;
				}
				(
					ALL
					{
						isAll = true;
					}
				)? 
				{
					beginSubQuery();
				}
				SELECT 
				(
					DISTINCT
					{
						builder.setDistinct();
					}
				)? columns
				FROM from 
				(join)*
				{
					builder.resolveColumns();
				}
				(where)?
				(GROUP BY groups)?
				(having)?
				{
					if (isAll){
						addUnionAll();
					} else {
						addUnion();
					}
				}
			;

orders		:	order (COMMA order)*;

order		:	expression 
				op = (ASC | DESC)?
				{
					if($op == null || $op.type == ASC){
						builder.addAsc();
					} else {
						builder.addDesc();
					}
				}
			;

subquery	:	L_BRACKET
				{
					beginSubQuery();
				}
				SELECT 
				(
					DISTINCT
					{
						builder.setDistinct();
					}
				)? columns
				FROM from 
				(join)*
				{
					builder.resolveColumns();
				}
				(where)?
				(GROUP BY groups)?
				(having)?
				(union)*
				R_BRACKET
				{
					endSubQuery();
				}
			;

expression	:	ID
				{
					builder.pushID($ID.text);
				}
			|	TABLE = ID PERIOD ID
				{
					builder.pushID($TABLE.text, $ID.text);
				}
			|	NUMBER
				{
					builder.pushNumber($NUMBER.text);
				}
			|	STRING
				{
					builder.pushString($STRING.text);
				}
			|	COLON ID
				{
					builder.pushParam($ID.text);
				}
			|	QUEMARK
				{
					builder.pushParam(nextParamIndex());
				}
			|	op = (COUNT | SUM | AVG | MAX | MIN) 
				L_BRACKET 
				{
					boolean isDistinct = false;
				}
				(
					DISTINCT
					{
						isDistinct = true;
					}
				)? expression R_BRACKET
				{
					if (isDistinct) {
						builder.pushDisAggregation($op.text);
					} else {
						builder.pushAggregation($op.text);
					}
				}
			|	ID L_BRACKET
				{
					int length = 0;
				} 
				(
					expression 
					{
						length++;
					}
					(
						COMMA expression
						{
							length++;
						}
					)*
				)? R_BRACKET
				{
					builder.pushFunction($ID.text, length);
				}
			|	subquery
			|	MINUS expression
				{
					builder.pushMinus();
				}
			|	L_BRACKET expression R_BRACKET
				{
					builder.pushBracket();
				}
			|	expression op = (ASTERISK | DIVISION) expression
				{
					if ($op.type == ASTERISK) {
						builder.pushMultiplication();
					} else {
						builder.pushDivision();
					}
				}
			|	expression op = (ADD | MINUS) expression
				{
					if ($op.type == ADD){
						builder.pushAdd();
					} else {
						builder.pushSubtraction();
					}
				}
			|	CASE 
				{
					int length = 0;
				}
				(
					WHEN condition THEN expression
					{
						length++;
					}
				)+ ELSE expression END
				{
					builder.pushIfCase(length);
				}
			|	CASE expression 
				{
					int length = 0;
				}
				(
					WHEN expression THEN expression
					{
						length++;
					}
				)+ ELSE expression END
				{
					builder.pushSwitchCase(length);
				}
			;

condition	:	expression op = (EQ | LT | GT | LTEQ | GTEQ | NOTEQ)
			 	(
		 			expression 
			 		|	o = (ANY | ALL) subquery
			 			{
			 				if ($o.type == ANY){
			 					builder.pushAny();
			 				} else {
			 					builder.pushAll();
			 				}
			 			}
			 	)
				{
					switch($op.type){
						case EQ:
							builder.pushEQ();
							break;
						case LT:
							builder.pushLT();
							break;
						case GT:
							builder.pushGT();
							break;
						case LTEQ:
							builder.pushLTEQ();
							break;
						case GTEQ:
							builder.pushGTEQ();
							break;
						default:
							builder.pushNotEQ();
							break;
					}
				}
			|	expression 
				{
					boolean isNot = false;
				}
				(
					NOT
					{
						isNot = true;
					}
				)? LIKE expression
				{
					if (isNot) {
						builder.pushNotLike();
					} else {
						builder.pushLike();
					}
				}
			|	expression 
				{
					boolean isNot = false;
				}
				(
					NOT
					{
						isNot = true;
					}
				)? 
				op = (LEFT | ALL) LIKE 
				(
						COLON ID
						{
							if ($op.type == LEFT){
								builder.pushLeftLikeParam($ID.text);
							} else {
								builder.pushAllLikeParam($ID.text);
							}
						} 
					|	QUEMARK
						{
							if ($op.type == LEFT){
								builder.pushLeftLikeParam(nextParamIndex());
							} else {
								builder.pushAllLikeParam(nextParamIndex());
							}
						}
				)
				{
					if (isNot) {
						builder.pushNotLike();
					} else {
						builder.pushLike();
					}
				}
			|	expression 
				{
					boolean isNot = false;
				}
				(
					NOT
					{
						isNot = true;
					}
				)?  IN (list | subquery)
				{
					if (isNot){
						builder.pushNotIn();
					} else {
						builder.pushIn();
					}
				}
			|	{
					boolean isNot = false;
				}
				(
					NOT
					{
						isNot = true;
					}
				)? EXISTS subquery
				{
					if (isNot){
						builder.pushNotExists();
					} else {
						builder.pushExists();
					}
				}
			|	expression IS 
				{
					boolean isNot = false;
				}
				(
					NOT
					{
						isNot = true;
					}
				)? NULL
				{
					if (isNot){
						builder.pushNotNull();
					}else{
						builder.pushNull();
					}
				}
			|	expression BETWEEN expression AND expression
				{
					builder.pushBetween();
				}
			|	condition AND condition
				{
					builder.pushAnd();
				}
			|	condition OR condition
				{
					builder.pushOr();
				}
			|	L_BRACKET condition R_BRACKET
				{
					builder.pushBracket();
				}
			;
			
list		:	L_BRACKET (listItem) 
				{
					int length = 1;
				}
				(
					COMMA listItem
					{
						length++;
					}
				)* R_BRACKET
				{
					builder.pushList(length);
				}
			;

listItem	:		COLON ID
					{
						builder.pushListParam($ID.text);
					} 
				|	QUEMARK
					{
						builder.pushListParam(nextParamIndex());
					}
				|	expression
			;
			
setValues	:	setValue (COMMA setValue)*;

setValue	:	ID EQ expression
				{
					builder.setValue($ID.text);
				}
			;
			
insert		:	insertValue (COMMA insertValue)*;

insertValue	:	ID EQ expression
				{
					builder.insertValue($ID.text);
				}
			;

SELECT		:	S E L E C T;
DISTINCT	:	D I S T I N C T;
AS			:	A S;
COUNT		:	C O U N T;
SUM			:	S U M;
AVG			:	A V G;
MAX			:	M A X;
MIN			:	M I N;
FROM		:	F R O M;
CHECK		:	C H E C K;
INNER		:	I N N E R;
LEFT		:	L E F T;
JOIN		:	J O I N;
ON			:	O N;
WHERE		:	W H E R E;
GROUP		:	G R O U P;
BY			:	B Y;
HAVING		:	H A V I N G;
UNION		:	U N I O N;
ALL			:	A L L;
ORDER		:	O R D E R;
ASC			:	A S C;
DESC		:	D E S C;
ANY			:	A N Y;
EXISTS		:	E X I S T S;
LIKE		:	L I K E;
IN			:	I N;
NOT			:	N O T;
IS			:	I S;
NULL		:	N U L L;
CASE		:	C A S E;
WHEN		:	W H E N;
THEN		:	T H E N;
ELSE		:	E L S E;
END			:	E N D;
BETWEEN		:	B E T W E E N;
AND			:	A N D;
OR			:	O R;

ASTERISK	:	'*';
COMMA		:	',';
PERIOD		:	'.';
COLON		:	':';
L_BRACKET	:	'(';
R_BRACKET	:	')';
MINUS		:	'-';
DIVISION	:	'/';
ADD			:	'+';
EQ			:	'=';
LT			:	'<';
GT			:	'>';
LTEQ		:	'<=';
GTEQ		:	'>=';
NOTEQ		:	'!=';
QUEMARK		:	'?';
ID			:	[a-zA-Z][a-zA-Z0-9_]*;
NUMBER		:	[0-9]+('.'[0-9]+)?;
STRING		:	'\'' (~'\''|'\\\'')* '\'';
WS			:	[ \t\r\n]+ -> skip;

fragment A : 'a' | 'A';
fragment B : 'b' | 'B';
fragment C : 'c' | 'C';
fragment D : 'd' | 'D';
fragment E : 'e' | 'E';
fragment F : 'f' | 'F';
fragment G : 'g' | 'G';
fragment H : 'h' | 'H';
fragment I : 'i' | 'I';
fragment J : 'j' | 'J';
fragment K : 'k' | 'K';
fragment L : 'l' | 'L';
fragment M : 'm' | 'M';
fragment N : 'n' | 'N';
fragment O : 'o' | 'O';
fragment P : 'p' | 'P';
fragment Q : 'q' | 'Q';
fragment R : 'r' | 'R';
fragment S : 's' | 'S';
fragment T : 't' | 'T';
fragment U : 'u' | 'U';
fragment V : 'v' | 'V';
fragment W : 'w' | 'W';
fragment X : 'x' | 'X';
fragment Y : 'y' | 'Y';
fragment Z : 'z' | 'Z';